<?php
/**
 * @package scottlib.buckets
 * @author Aaron Collegeman aaroncollegeman.com
 * @version 1.0.0-BETA
 * 
 * Copyright (C)2009 Collegeman.net, LLC.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 */

///////////////////////////////////////////////////////////////////////////////
// The file defines the slData class and its dependencies.
///////////////////////////////////////////////////////////////////////////////

final class slData {
	
	private $data = array();
	
	private $replace = array();
	
	private $item;
	
	private $domain;
	
	private $engine;
	
	function __construct($domain = null, $item = null, $data = null, slBigtableEngine $engine = null) {
		if (is_array($domain)) {
			$data = $domain;
			$domain = 'default';
		}
		
		else if (is_array($item)) {
			$data = $item;
			$item = null;
		}
		
		$this->item = ($item ? $item : sl_guid());
		$this->domain = ($domain ? $domain : 'default');
		
		if ($data)
			$this->bind($data);
			
		$this->engine = ($engine != null ? $engine : slBigtableEngine::instance());
	}
	
	final function item() {
		return $this->item;
	}
	
	final function domain() {
		return $this->domain;
	}
	
	private final function hasSortedCue($name) {
		$sorted = false;
		
		// if $name bears suffix "_sorted", remove the suffix and raise the flag
		if (($at = strrpos($name, '_sorted')) === strlen($name)-7) {
			$sorted = true;
			$name = substr($name, 0, $at);
		}
		
		return array($name, $sorted);
	}
	
	private final function hasSerializedCue($name) {
		$serialized = false;
		
		// if $name bears prefix "serialized_", remove the suffix and raise the flag
		if (($at = strrpos($name, 'serialized_')) === 0) {
			$serialized = true;
			$name = substr($name, 0, $at);
		}
		
		return array($name, $serialized);
	}
	
	final function __set($name, $value) {
		//echo "<div>".print_r($value, true)."</div>";
		
		list($name, $pad) = $this->hasSortedCue($name);
		list($name, $serialize) = $this->hasSerializedCue($name);
		
		// prepare the cell
		if (!array_key_exists($name, $this->data) || $value instanceof slReplacement)	
			$this->data[$name] = array();
			
		// if value is wrapped in slReplacement, clear the data stored there
		if ($value instanceof slReplacement) {
			$value = $value->get();	
			$this->replace[] = $name;
		}
			
		// if value is an object, check for serialization and enforce
		if (is_object($value)) {
			// if serialization requested by property name, force it
			// this is really for preventing confusion more than anything else...
			if ($serialize && !($value instanceof slSerialized)) {
				$this->data[$name][] = new slSerialized($value);
			}
			else {
				if ($value instanceof slSerialized) { // data already serialized, put it in
					$this->data[$name][] = $value;
				}
				else {
					$this->data[$name][] = new slSerialized($value);
				}
			}
		}
			
		// indexed arrays should have all their values added to the attribute
		// hashes are serialized
		else if (is_array($value)) {
			if ($serialize || !array_all_keys_numeric($value)) {
				$this->data[$name][] = new slSerialized($value);
			}
			else {
				foreach($value as $v) {
					$this->data[$name][] = ($pad ? $this->engine->pad($v) : $v);	
				}
			}
		}

		// all other values
		else {
			$this->data[$name][] = ($pad ? $this->engine->pad($value) : $value);
		}
	}
	
	final function __get($name) {
		
		if ($name == "itemName") {
			return $this->item();
		}
		
		list($name, $unpad) = $this->hasSortedCue($name);
		list($name, $unserialize) = $this->hasSerializedCue($name);
			
		$stored = array_key_exists($name, $this->data) ? $this->data[$name] : array();
		
		$values = array();
		
		foreach($stored as $v) {
			if ($v instanceof slSerialized)
				$v = $v->get();
				
			if ($unserialize)
				$v = unserialize($v);
				
			if ($unpad)
				$v = $this->engine->unpad($v);
			
			$values[] = $v;
		}
		
		if (count($values) == 1)
			$values = $values[0];
		
		return $values;
	}
	
	final function data() {
		return $this->data;
	}
	
	final function replacements() {
		return $this->replace;
	}
	
	final function save() {
		$this->engine->save($this);
	}
	
	final function replaceAll() {
		$this->replace = array_merge($this->replace, array_keys($this->data));
		$this->engine->save($this);
	}
	
	final function bind($arr, $exceptions = array()) {
		foreach($arr as $prop => $val) {
			if (!in_array($prop, $exceptions))
				$this->$prop = $val; 
		}
	}
	
}

/**
 * A wrapper for data that should completely replace all data at the requested attribute.
 * @since 1.0.0-BETA
 */
final class slReplacement {

	private $value;
	
	function __construct($value) {
		$this->value = $value;
	}
	
	final function get() {
		return $this->value;
	}
	
}

/**
 * A wrapper for serialized data.
 * @since 1.0.0-BETA
 */
final class slSerialized {

	private $value;
	
	function __construct($obj, $serialized = false) {
		$this->value = ($serialized ? unserialize($obj) : $obj);
	}
	
	final function serial() {
		return serialize($this->value);
	}
	
	final function get() {
		return $this->value;
	}
 	
}

///////////////////////////////////////////////////////////////////////////////
// Function definitions
///////////////////////////////////////////////////////////////////////////////

if (!function_exists("data")) {
	function data($domain, $item = null, $data = null, $engine = null) {
		return new slData($domain, $item, $data, $engine);
	}
}

if (!function_exists("replacement")) {
	function replacement($value) {
		return new slReplacement($value);
	}
}

if (!function_exists("packed")) {
	function packed($value) {
		return new slSerialized($value);
	}
}